20.Gün - SwiftUI Proje-2 Bölüm-1
Table of Contents
Bu proje ile beraber SwiftUI’de ilerlemeye devam ediyoruz. VStack,
Image
, LinearGradient
gibi yeni konuları öğreneceğiz. Bu bölümün uygulaması GuessTheFlag isimli bayrak tahmin uygulaması olacak. Fakat uygulamaya geçmeden önce bazı temel bilgileri öğrenmemiz gerekiyor.
SwiftUI Stack Kullanımı #
Ekrandaki birden fazla view öğesi ile ilgileniyorsak üç tane kullanışlı yolumuz var; HStack
, VStack
ve ZStack
bunlar yatay, dikey ve derinlik ile ilgilenirler.
İlk SwiftUI projesi oluşturduğumuzda şu şekilde görünür;
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Text("Hello, world!")
}
.padding()
}
Yukarıdaki kod, tek bir tür view döndürür o da text view’dır. Eğer üst üste iki text view döndürmek isteseydik şöyle yazabilirdik;
var body: some View {
Text("Hello, world!")
Text("This is another text view")
}
Veya VStack kullanarak şu şekilde de yazabilirdik;
var body: some View {
VStack {
Text("Hello, world!")
Text("This is inside a stack")
}
}
Her iki text view bir VStack
içine yerleştirilmiştir. Sonuç aynı gibi gözükebilir fakat üç önemli fark vardır;
- view’lar arasından ne kadar boşluk bırakabileceğimizi belirtmemize olanak tanır.
- view’ları birbirinin soluna, sağına veya ortasına yerleştirip yerleştirelemeyeceği gibi alignment (hizalama) belirtmemize olanak tanır.
VStack
kullanmasaydık, SwfitUI view’ları düzenlemekte serbest olacaktı. Örneğin, daha büyük ekranda iki text view yan yana görünebilirdi.
Vstack
varsayılan olarak iki view arasına otomatik bir miktar boşluk(spacing) bırakır, ancak stack’deki boşluk miktarını kontrol edebiliriz;
VStack(spacing: 20) {
Text("Hello, world!")
Text("This is inside a stack")
}
Varsayılan olarak, VStack
view’ları centered (ortalanmış) olarak hizalar, ancak bunu alignment
property ile kontrol edebiliriz. Örneğin bu text view’ları .leading
kullanarak sola hizalı şekilde kullanabiliriz.
VStack(alignment: .leading) {
Text("Hello, world!")
Text("This is inside a stack")
}
VStack
ile view’ları dikey olarak hizalamanın yanı sıra, yatay olarak hizalamalar için HStack
kullanabiliriz. HStack
boşluk ve alignment da dahil olmak üzere VStack
ile aynı söz dizilimine sahiptir.
HStack(spacing: 20) {
Text("Hello, world!")
Text("This is inside a stack")
}
Dikey ve yatay stack’ler içeriklerini otomatik olarak sığdırır ve kendilerini mevcut alanın ortasına hizalamayı tercih eder. Bu durumu değiştirmek ister ve tüm stack içeriğini bir tarafa itmek istersek bir veya daha fazla Spacer
kullanabiliriz. Örneğin VStack
’in sonuna bir Spacer
eklersek tüm view’ları ekranın en üstüne iter.
VStack {
Text("First")
Text("Second")
Text("Third")
Spacer()
}
Birden fazla Spacer
eklersek, mevcut alan aralarında bölünür. Örneğin, aşağıdaki kod ile boşluğun 1/3’ünü üstte ve 2/3’ünü altta kullanabiliriz.
VStack {
Spacer()
Text("First")
Text("Second")
Text("Third")
Spacer()
Spacer()
}
Ayrıca view’ları derinliğe göre düzenlemek için Ztack
kullanabiliriz. ZStack
üst üste binen view’lar oluşturur. İki text view söz konusu olduğunda, bu durum okumayı oldukça zolaştıracaktır;
ZStack {
Text("Hello, world!")
Text("This is inside a stack")
}
ZStack
’te view’lar üst üste bindiğinden, Spacer
yoktur, ancak alignment özelliğine sahiptir. Dolayısıyla ZStack
içinde bir büyük ve bir küçük iki görünüm varsa, şu şekilde üste hizalanmasını sağlayabiliriz; ZStack(alignment: .top) {
ZStack
, içeriğini yukarıdan aşağıya ve arkadan öne doğru çizer. Bu durumda bir resmimiz ve ardından bazı metinlerimiz varsa ZStack
’in bunları bu sırayla çizeceği ve metni resmin üstüne yerleştireceği anlamına gelir.
VStack
ve HStack
3x3’lük bir grid oluşturalım;
SwiftUI Color ve Frame #
SwiftUI renkler ile işlem yapmak için bize basit ve güçlü özellikler sunuyor.
ZSTack
içerisinde bulunan bir text ile başlayalım;
ZStack {
Text("Your content")
}
text’in arkasına kırmızı bir şey koymak istiyorsak ne yapacağız?
Seçeneklerimizden biri background()
modifier’ını kullanmak. Bu modifier aşağıdaki gibi bir renk çizebilir;
ZStack {
Text("Your content")
}
.background(.red)
Bu beklediğimiz şeyi yapmış gibi gözüküyor fakat bir fark var. Biz tüm ZStack
’in arka plan rengini değiştirmek istesek de yalnızca text’in arka planı rengi değişti.
Aslında yukarıdaki kod ile aşağıdakinin arasında bir fark yok;
ZStack {
Text("Your content")
.background(.red)
}
Metnin arkasındaki tüm alanı kırımızı renk ile doldurmak istiyorsak, rengi ZStack
içerisine yerleştirmeliyiz. Renk tek başına bir view olarak ele alınır.
ZStack {
Color.red
Text("Your content")
}
Color.red
kendi başına bir view’dır. Bu sebeple şekiller ve text gibi kullanılabilir.
background()
modifier’ı kullanırken, SwiftUI.red
’in aslındaColor.red
anlamına geldiğini anlayabiliyordu. Rengi bağımsız bir view olarak kullandığımızda, Swift’in.red
’in ne anlama geldiğini anlamasını sağlayacak bir context (bağlam) yoktur, bu sebepleColor.red
şeklinde kullanmalıyız.
Renkler otomatik olarak mevcut tüm alanı kaplar, ancak belirli boyutlar istemek için frame()
modifier’ını kullanabiliriz. Örneğin, aşağıdaki gibi 200x200 kırmızı bir kare isteyebiliriz.
Color.red
.frame(width: 200, height: 200)
frame()
in değerlerini istediğimiz gibi değiştirebiliriz. Örneğin, minimum 200pt genişliğinde fakat mümkünse en fazla genişliği kullansın ve en fazla 200pt yüksekliğinde bir frame’i şu şekilde oluşturabiliriz.
Color.red
.frame(minWidth: 200, maxWidth: .infinity, maxHeight: 200)
SwiftUI bize Color.blue
, Color.green
, Color.indigo
vb. gibi bir dizi yerleşik renk sunar. Ayrıca amaca yönelik kullanabileceğimiz renklerimizde bulunmaktadır.
Örneğin; Color.primary
SwiftUI’deki metnin varsayılan rengidir ve kullanıcının cihazının açık modda mı yoksa koyu modda mı çalıştığına bağlı olarak siyah veya beyaz olacaktır. Ayrıca, cihaza bağlı olarak siyah veya beyaz olan Color.secondary
de vardır, ancak bu hafif bir şeffaflığa sahiptir, böylece arkasındaki rengin bir kısmı parlar.
İstersek kendi özel rengimizi de şu şekilde oluşturabiliriz;
Color(red: 1, green: 0.8, blue: 0)
Color.red
kullanımında, ekranın altında ve üstünde beyaz alanların kaldığını göreceğiz. Bu alan kasıtlı olarak boş bırakılmıştır. Kalan kısım (yani kırmızı alan) safe area olarak isimlendirilmektedir. iPhone’daki çentik (notch) veya dynamic island tarafından kırpıla endişesi olmadan bu alanda çizim yapabiliyoruz.
İçeriğimizin safe area ‘yı da kullanmasını istiyorsak .ignoresSafeArea()
modifier’ını kullanabiliriz.
ZStack {
Color.red
Text("Your content")
}
.ignoresSafeArea()
Önemli hiçbir içeriğin safe area dışına yerleştirilmemesi çok önemlidir, çünkü kullanıcının içeriği görmesini oldukça zorlaştırır.
İçeriğimiz sadece dekoratif amaçlı ise, buradaki arka plan rengimiz gibi, onu güvenli alanın dışına uzatmamızda bir sakınca yoktur.
background()
modifier ile .red
.green
gibi sabit renkleri kullanmanın yanı sıra material de kullanabiliriz. material sayesinde buzlu cam efekti uygulayarak derinlik efektleri oluşturabiliriz.
Bunu çalışırken görelim;
ZStack {
VStack(spacing: 0) {
Color.red
Color.blue
}
Text("Your content")
.foregroundStyle(.secondary)
.padding(50)
.background(.ultraThinMaterial)
}
.ignoresSafeArea()
SwiftUI Gradient #
SwiftUI bize dört çeşit gradient sunmaktadır ve renkler gibi bunlar da kullanıcı arayüzüne çizilebilen view’lardır.
Gradient’i oluşturan bileşenler;
- gösterilecek renklerden oluşan array
- boyut ve yön bilgileri
- Kullanılacak gradient türü
Örneğin, linear gradient tek bir yöne gider, bu sebeple ona aşağıdaki gibi bir başlangıç noktası belirleriz.
LinearGradient(colors: [.white, .black], startPoint: .top, endPoint: .bottom)
İstersek gradientimize ara noktalar da belirleyebiliriz. Örneğin, gradient’in başlangıçtan kullanılabilir alanın %45’ine kadar beyaz, ardından kullanılabilir alanın %55’inden itibaren siyah olmasını sağlayabiliriz.
LinearGradient(stops: [
Gradient.Stop(color: .white, location: 0.45),
Gradient.Stop(color: .black, location: 0.55),
], startPoint: .top, endPoint: .bottom)
Bu daha keskin bir gradient oluşturacaktır, merkezdeki küçük bir alana sıkışacaktır.
Swfit burada gradient stop oluşturduğumuzu biliyor, bu sebeple kısayol olarak Gradient.Stop
yerine sadece .init
yazabiliriz.
LinearGradient(stops: [
.init(color: .white, location: 0.45),
.init(color: .black, location: 0.55),
], startPoint: .top, endPoint: .bottom)
Alternatif olarak radial gradinet ile daire şeklinde dışa doğru gradientler oluşturabiliriz. Bu sebeple yön belirtmek yerine başlangıç ve bitiş yarı çapı belirtiriz. Örneğin;
RadialGradient(colors: [.blue, .black], center: .center, startRadius: 20, endRadius: 200)
Son olarak açısal gradient olarak adlandırılan bir gradient çeşidi daha vardır. Bazı yerlerde konik gradient olarak da adlandırılabilir.
Örnek olarak şunu kullanabiliriz;
AngularGradient(colors: [.red, .yellow, .green, .blue, .purple, .red], center: .center)
Tüm bu gradient türleri basit renkler yerine stop’lara sahip olabilir. Ayrıca, layout’larımızda bağımsız view’lar olarak çalışabilir veya bir modifier’ın parçası olarak kullanılabilirler.
SwiftUI ayrıca yukarı türlerden başka daha basit olan dördüncü gradient türünü de barındırır. Fakat üzerinde herhangi bir kontrolümüz yoktur ve ayrıca bunları tek tek view’lar yerine yalnızca background ve foreground stilleri olarak kullanabiliriz.
Bu tür, herhangi bir renkten sonra .gradient
eklenerek oluşturulur.
Text("Your content")
.frame(maxWidth: .infinity, maxHeight: .infinity)
.foregroundStyle(.white)
.background(.black.gradient)
Bu tür bir gradient çok incedir ancak neredeyse hiç uğraşmadan tasarımımıza farklı bir hava katmamımızı sağlar.
SwiftUI Butonlar ve Resimler #
Bir buton oluşturmanın en basit yolu, butonun title’ını ve butona dokunulduğunda çalıştırılması için gereken bir clousure’u sağlamaktır.
Button("Delete selection") {
print("Now deleting…")
}
Elbette action için bir closure yerine bir fonksiyon da kullanabiliriz.
struct ContentView: View {
var body: some View {
Button("Delete selection", action: executeDelete)
}
func executeDelete() {
print("Now deleting…")
}
}
Butonların görünümünü özelleştirmenin birkaç farklı yolu vardır. İlk olarak butona iOS’un hem görsel hem de ekran okuyucular için görünümünü ayarlamak üzere kullanabileceğimiz bir rol ekleyebiliriz. Örneğin silme butonumuzun destructive bir rolü olduğunu söyleyebiliriz.
Button("Delete selection", role: .destructive, action: executeDelete)
İkinci olarak butonlar için yerleşik stillerden birini kullanabiliriz: .bordered
ve .borderedProminent
VStack {
Button("Button 1") { }
.buttonStyle(.bordered)
Button("Button 2", role: .destructive) { }
.buttonStyle(.bordered)
Button("Button 3") { }
.buttonStyle(.borderedProminent)
Button("Button 4", role: .destructive) { }
.buttonStyle(.borderedProminent)
}
bordered butonun renklerini özelleştirmek için tint()
modifier’ını kullanabiliriz.
Button("Button 3") { }
.buttonStyle(.borderedProminent)
.tint(.mint)
Apple, çok fazla prominent (belirgin) buton kullanılmamasını açıkça tavsiye etmektedir, çünkü her şey belirgin olduğunda hiçbir şey belirgin değildir.
Tamemen özel bir buton yapmak istiyorsak, ikinci bir closure kullanarak özel bir title iletebiliriz
Button {
print("Button was tapped")
} label: {
Text("Tap me!")
.padding()
.foregroundStyle(.white)
.background(.red)
}
SwiftUI uygulamalarımızdaki resimleri işlemek için özel bir Image
türüne sahiptir. Image
oluşturmanın üç yolu vardır;
Image(”pencil”)
, projemize eklediğimiz “Pencil” isimli bir görüntüyü getirecektir.Image(decorative: “pencil”)
aynı görüntüyü yükler, ancak ekran okuyucuyu etkinleştiren kullanıcılar bunu okumaz. Bu, önemli bilgiler iletmeyen resimler için kullanışlıdır.Image(systemName: "pencil")
iOS’ta yerleşik olarak bulunan pencil (kalem) simgesini getirecektir. Bu, Apple’ın SF Symbols simge koleksiyonunu kullanır.
Varsayılan olarak, ekran okuyucu (screen reader) etkinleştirilmişse resmimizin adını okuyacaktır, bu sebeple kullanıcının kafasını karıştırmak istemiyorsak resimlerimize net isimler vermeliyiz. Yada Image(decorative:)
initializer’ını kullanmalıyız.
Butonlar ile birlikte Image
’i kullanabiliriz;
Button {
print("Edit button was tapped")
} label: {
Image(systemName: "pencil")
}
Buton içinde aynı anda hem metin hem de görüntü istiyorsak iki seçeneğimiz vardır. Birincisi her ikisini de doğrudan Button
’a sağlamaktır.
Button("Edit", systemImage: "pencil") {
print("Edit button was tapped")
}.buttonStyle(.bordered)
Fakat daha özel bir şey istiyorsak, SwiftUI’nin Label
adındaki özel türünü kullanabiliriz.
Button {
print("Edit button was tapped")
} label: {
Label("Edit", systemImage: "pencil")
.padding()
.foregroundStyle(.white)
.background(.red)
}
SwiftUI Alert Message #
Önemli bir şey olduğunda, kulanıcıyı bilgilendirmenin yaygın bir yolu alert kullanmaktır. alert bir başlık (title), mesaj ve duruma bağlı olarak bir veya iki butondan oluşur.
alert’i bir değişkene atayıp myAlert.show()
diyemeyiz, çünkü bu durum SwiftUI’nin mantığına biraz terstir.
Bunun yerine, aşağıdaki gibi alert’in gösterilip gösterilmediğini izleyen bir state oluştururuz.
@State private var showingAlert = false
Daha sonra alert’i kullanıcı arayüzüne ekleriz ve alert’in gösterilip gösterilmeyeceğini belirlemek için bu state’i kullanmasını söyleriz. SwiftUI showingAlert
’i izleyecek ve true olur olmaz alert’i gösterecektir.
Tüm bunları bir araya getirerek kodumuzu oluşturalım;
struct ContentView: View {
@State private var showingAlert = false
var body: some View {
Button("Show Alert") {
showingAlert = true
}
.alert("Important message", isPresented: $showingAlert) {
Button("OK") { }
}
}
}
Yukarıdaki kod alert’i buton’a ekler, fakat burada .alert()
modifier’ının nerede kullanıldığının bir önemi yoktur. Tek yaptığımız, bir alert’in var olduğunu ve showingAlert
’in true olduğunda gösterildiğini söylemektir.
.alert()
modifier’a yakından bakalım;
alert("Important message", isPresented: $showingAlert)
İlk kısım alert’in başlığıdır. SwiftUI, alert kapatıldığında showingAlert
’i otomatik olarak false yapacağından two-way binding vardır.
Şimdi de buton’a bakalım;
Button("OK") { }
Burada boş bir closure var. Yani butona basıldığında çalışacak herhangi bir işlevsellik eklemiyoruz. Yine de bu önemli değil, çünkü bir alert içindeki herhangi bir buton alert’i otomatik olarak kapatacaktır. Bu closure alert’i kapatmanın ötesinde herhangi bir ekstra işlevsellik eklememize izin vermek için oradadır.
Alert’e daha fazla buton ekleyebiliriz. Ayrıca burası her butonun ne işe yaradığının açıkça belirtmemiz için özellikle iyi bir yerdir.
.alert("Important message", isPresented: $showingAlert) {
Button("Delete", role: .destructive) { }
Button("Cancel", role: .cancel) { }
}
Son olarak, başlığımızla beraber aşağıdaki gibi bir closure kullanarak mesaj metni ekleyebiliriz
Button("Show Alert") {
showingAlert = true
}
.alert("Important message", isPresented: $showingAlert) {
Button("OK", role: .cancel) { }
} message: {
Text("Please read this.")
}
Bu yazıyı İngilizce olarak da okuyabilirsiniz.
You can also read this article in English.